home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995…tember: Reference Library / Dev.CD Sep 95 RL / Dev.CD Sep 95 RL.toast / mac / Technical Documentation / develop / develop Issue 6 code / TCP / NewsWatcher / NW Source / Source / full.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-05-23  |  24.2 KB  |  906 lines  |  [TEXT/MMCC]

  1. /*----------------------------------------------------------------------------
  2.  
  3.     full.c
  4.  
  5.     This module handles tasks involving the full group list.
  6.     
  7.     Copyright © 1994-1995, Northwestern University.
  8.  
  9. ----------------------------------------------------------------------------*/
  10.  
  11. #include <string.h>
  12. #include <stdio.h>
  13.  
  14. #include "glob.h"
  15. #include "full.h"
  16. #include "qsort.h"
  17. #include "dialog.h"
  18. #include "news.h"
  19. #include "newswatcher.h"
  20. #include "menus.h"
  21. #include "status.h"
  22. #include "wind.h"
  23. #include "group.h"
  24. #include "text.h"
  25. #include "memutil.h"
  26. #include "strutil.h"
  27. #include "windutil.h"
  28. #include "subscribe.h"
  29. #include "listutil.h"
  30. #include "fileutil.h"
  31.  
  32.  
  33.  
  34. #define kMustCloseBeforeRebuildDlg        150
  35.  
  36.  
  37.  
  38. static Handle gSortGroupNames;
  39.  
  40.  
  41.  
  42. /*----------------------------------------------------------------------------
  43.     FindGroupIndex 
  44.     
  45.     Find the index of a group in the full group array gFullGroupArray.
  46.  
  47.     Entry:    name = group name.
  48.             gFullGroupArray sorted in increasing order by group name.
  49.  
  50.     Exit:    function result = index in gFullGroupArray of group, or -1 if not found.
  51. ----------------------------------------------------------------------------*/
  52.  
  53. short FindGroupIndex (char *name)
  54. {
  55.     long low = 0;
  56.     long high = gNumGroups-1;
  57.     long mid;
  58.     short compare;
  59.     
  60.     while (low <= high) {
  61.         mid = (low + high) >> 1;
  62.         compare = strcmp(name, *gGroupNames + (*gFullGroupArray)[mid].nameOffset);
  63.         if (compare == 0) {
  64.             return mid;
  65.         } else if (compare < 0) {
  66.             high = mid-1;
  67.         } else {
  68.             low = mid+1;
  69.         }
  70.     }
  71.     return -1;        
  72. }
  73.  
  74.  
  75.  
  76. /*----------------------------------------------------------------------------
  77.     GroupCompare 
  78.     
  79.     The group comparison routine used in the calls to FastQSort in 
  80.     SortFullGroupArray and CheckForNewGroups below. It does a 
  81.     simple string compare and gives time to background applications.
  82.     
  83.     Entry:    p = pointer to first group info struct.
  84.             q = pointer to second group info struct.
  85.             gSortGroupNames = handle to group names block.
  86.             
  87.     Exit:    function result = error code.
  88.             *result
  89.                 < 0 if first group name < second group name.
  90.                 = 0 if first group name = second group name.
  91.                 > 0 if first group name > second group name.
  92. ----------------------------------------------------------------------------*/
  93.  
  94. static OSErr GroupCompare (TGroup *p, TGroup *q, short *result)
  95. {
  96.     OSErr err;
  97.     static short counter = 0;
  98.  
  99.     if ((++counter & 0x1f) == 0) {
  100.         err = GiveTime(false);
  101.         if (err != noErr) return err;
  102.         counter = 0;
  103.     }
  104.  
  105.     *result = strcmp(*gSortGroupNames + p->nameOffset, 
  106.         *gSortGroupNames + q->nameOffset);
  107.     return noErr;
  108. }
  109.  
  110.  
  111.  
  112. /*----------------------------------------------------------------------------
  113.     SortFullGroupArray 
  114.     
  115.     Sort a full group array alphabetically by group name.
  116.     
  117.     Exit:    function result = error code.
  118. ----------------------------------------------------------------------------*/
  119.  
  120. static OSErr SortFullGroupArray (TGroup **groupArray, short numGroups, Handle groupNames)
  121. {
  122.     OSErr err = noErr;
  123.     char state;
  124.  
  125.     if (numGroups > 0) {
  126.         err = DisplayStatusMessageNumber(kStrSortingStatusMsg);
  127.         if (err != noErr) return err;
  128.         gSortGroupNames = groupNames;
  129.         state = MyHGetState(groupArray);
  130.         MyHLock(groupArray);
  131.         err = FastQSort(*groupArray, numGroups, sizeof(TGroup), 
  132.             (SortCmpFunction)GroupCompare);
  133.         MyHSetState(groupArray, state);
  134.         if (err != noErr) return err;
  135.     }
  136.     return noErr;
  137. }
  138.  
  139.  
  140.  
  141. /*----------------------------------------------------------------------------
  142.     InitializeGroupRecord 
  143.     
  144.     Initialize a new group record.
  145.     
  146.     Entry:    x = pointer to group record.
  147.             offset = offset in gGroupNames of group name.
  148. ----------------------------------------------------------------------------*/
  149.  
  150. static void InitializeGroupRecord (TGroup *x, long offset)
  151. {
  152.     x->nameOffset = offset;
  153.     x->firstMess = x->lastMess = 0;
  154.     x->numUnread = 0;
  155.     x->status = ' ';
  156.     x->unread = nil;
  157.     x->onlyRedrawCount = false;
  158. }
  159.  
  160.  
  161.  
  162. /*----------------------------------------------------------------------------
  163.     ReadGroupsFromPrefs 
  164.     
  165.     Read the full group list stored on the preferences file.
  166.     
  167.     Entry:    fSpec = pointer to file spec of prefs file.
  168.             prefsInDataFork = true if prefs are in data fork of prefs file
  169.                 and the full group list follows the prefs. False if the
  170.                 prefs are in the resource fork and the data fork contains
  171.                 only the full group list.
  172.             prefsVersion = version number of prefs file.
  173.  
  174.     Exit:    function result = error code.
  175. ----------------------------------------------------------------------------*/
  176.  
  177. OSErr ReadGroupsFromPrefs (FSSpec *fSpec, Boolean prefsInDataFork, 
  178.     unsigned long prefsVersion)
  179. {
  180.     OSErr err = noErr;
  181.     short fRefNum = 0, i;
  182.     Boolean needSort = false;
  183.     char *p, *pEnd;
  184.     char *prevName = nil;
  185.     char *curName;
  186.     TGroup *x;
  187.     long groupNamesSize;
  188.     char state;
  189.  
  190.     err = DisplayStatusMessageNumber(kStrReadingStatusMsg);
  191.     if (err != noErr) goto exit;
  192.         
  193.     err = FSpOpenDF(fSpec, fsRdPerm, &fRefNum);
  194.     if (err != noErr) return noErr;
  195.  
  196.     /* Read the saved group names. */
  197.     
  198.     err = GetEOF(fRefNum, &groupNamesSize);
  199.     if (err != noErr) goto exit;
  200.     if (prefsInDataFork) {
  201.         groupNamesSize -= sizeof(TPrefRec);
  202.         if (groupNamesSize < 0) goto exit;
  203.         err = SetFPos(fRefNum, fsFromStart, sizeof(TPrefRec));
  204.         if (err != noErr) goto exit;
  205.     }
  206.     err = MyNewHandle(groupNamesSize, &gGroupNames);
  207.     if (err != noErr) goto exit;
  208.     state = MyHGetState(gGroupNames);
  209.     MyHLock(gGroupNames);
  210.     err = FSRead(fRefNum, &groupNamesSize, *gGroupNames);
  211.     MyHSetState(gGroupNames, state);
  212.     if (err != noErr) goto exit;
  213.     MyFSClose(fRefNum, nil);
  214.     fRefNum = 0;
  215.     
  216.     /* Special case the Cornell version 2.0d15-CU, which put 
  217.        a 32 byte authorization username and 32 bytes of zero at 
  218.        the end of the NU 2.0d14 prefs, which in any NU version 
  219.        of NW shows up as the first 64 bytes of what we think 
  220.        is the full group list! (Yes, yuck). 
  221.        
  222.        First we check prefsVersion to see if it is 2.0d14. If 
  223.        it is, we then check byte 32 of the full group list. If 
  224.        byte 32 is 0, we assume this is a Cornell prefs file. In 
  225.        this case, we copy the first 32 bytes (the Cornell authorization 
  226.        username) to gPrefs.authUsername (our authorization username). 
  227.        We discard the next unused 32 zero bytes. 
  228.        
  229.        Note that in NU prefs files, a 0 byte never appears in the
  230.        full group list, so theoretically there is no chance of
  231.        confusing an NU prefs file with a CU prefs file.
  232.        
  233.        This makes NU NewsWatcher 2.0d27 and later understand and 
  234.        properly convert 2.0d15-CU prefs files. The reverse will never 
  235.        work - 2.0d15-CU is not able to make any sense of any NU version 
  236.        prefs file.
  237.     */
  238.  
  239.     if (prefsVersion == 0x02002014 && groupNamesSize >= 64 &&
  240.         *(*gGroupNames+32) == 0) 
  241.     {
  242.         BlockMoveData(*gGroupNames, gPrefs.authUsername, 32);
  243.         groupNamesSize -= 64;
  244.         BlockMoveData(*gGroupNames + 64, *gGroupNames, groupNamesSize);
  245.         MySetHandleSize(gGroupNames, groupNamesSize);
  246.         gFullGroupListDirty = true;
  247.     }    
  248.     
  249.     /* Check to make certain the last group name ends in CR. If not, strip
  250.        any trailing junk. */
  251.        
  252.     p = *gGroupNames + groupNamesSize - 1;
  253.     while (p >= *gGroupNames && *p != CR) p--;
  254.     p++;
  255.     if (p < *gGroupNames + groupNamesSize) {
  256.         groupNamesSize = p - *gGroupNames;
  257.         MySetHandleSize(gGroupNames, groupNamesSize);
  258.         gFullGroupListDirty = true;
  259.         if (p == *gGroupNames) return noErr;
  260.     }
  261.     
  262.     /* Walk through the gGroupNames buffer. Count the number of groups. 
  263.        Change all CR to 0. Check to see if the groups are already sorted
  264.        (they should be). */
  265.        
  266.     p = *gGroupNames;
  267.     pEnd = p + groupNamesSize;
  268.     gNumGroups = 0;
  269.     while (p < pEnd && gNumGroups < 16000) {
  270.         curName = p;
  271.         while (*p != CR) p++;
  272.         *p++ = 0;
  273.         gNumGroups++;
  274.         needSort = needSort || (prevName != nil && strcmp(prevName, curName) > 0);
  275.         prevName = curName;
  276.     }
  277.     
  278.     if (p < pEnd) {
  279.         groupNamesSize = p - *gGroupNames;
  280.         MySetHandleSize(gGroupNames, groupNamesSize);
  281.         gFullGroupListDirty = true;
  282.         ErrorMessageNumber(kStrTooManyPrefsFileGroups);
  283.     }
  284.     
  285.     /* Allocate and initialize the full group array. */
  286.     
  287.     err = MyNewHandle(gNumGroups*sizeof(TGroup), &gFullGroupArray);
  288.     if (err != noErr) goto exit;
  289.     
  290.     for (i = 0, x = *gFullGroupArray, p = *gGroupNames; i < gNumGroups; i++, x++) {
  291.         InitializeGroupRecord(x, p - *gGroupNames);
  292.         p += strlen(p) + 1;
  293.     }
  294.     
  295.     /* If necessary, sort the full group array. */
  296.  
  297.     if (needSort) {
  298.         gFullGroupListDirty = true;
  299.         err = SortFullGroupArray(gFullGroupArray, gNumGroups, gGroupNames);
  300.         if (err != noErr) goto exit;
  301.     }
  302.     
  303.     return noErr;
  304.     
  305. exit:
  306.  
  307.     MyDisposeHandle(gGroupNames);
  308.     if (fRefNum != 0) MyFSClose(fRefNum, nil);
  309.     gNumGroups = 0;
  310.     return err;
  311. }
  312.  
  313.  
  314.  
  315. /*----------------------------------------------------------------------------
  316.     AdjustFullGroupListChildWindows
  317.  
  318.     This function must be called whenever the full group list changes. It locates
  319.     all the open child subject list windows. If a group has been deleted, any
  320.     associated open child subject list window is closed. If a group still exists,
  321.     the "parentGroup" backpointer in the child window's TWindow info is adjusted
  322.     to point to the new location of the parent group in the full group array
  323.     gFullGroupArray.
  324.     
  325.     Exit:    function result = error code.
  326. ----------------------------------------------------------------------------*/
  327.  
  328. static OSErr AdjustFullGroupListChildWindows (void)
  329. {
  330.     TWindow **info, **childInfo;
  331.     TChild **childList, **prevChildList;
  332.     WindowPtr childWindow;
  333.     CStr255 groupName;
  334.     short index;
  335.     OSErr err = noErr;
  336.     
  337.     gFullGroupListDirty = true;
  338.     if (gFullGroupWindow == nil) return noErr;
  339.     info = (TWindow**)GetWRefCon(gFullGroupWindow);
  340.     childList = (**info).childList;
  341.     prevChildList = nil;
  342.     while (childList != nil) {
  343.         childWindow = (**childList).childWindow;
  344.         childInfo = (TWindow**)GetWRefCon(childWindow);
  345.         strcpy(groupName, *gGroupNames + (**childInfo).groupNameOffset);
  346.         index = FindGroupIndex(groupName);
  347.         if (index == -1) {
  348.             /* Group has been deleted. Close the child window. */
  349.             err = DoClose(childWindow);
  350.             if (err != noErr) return err;
  351.             if (prevChildList == nil) {
  352.                 childList = (**info).childList;
  353.             } else {
  354.                 childList = (**prevChildList).next;
  355.             }
  356.         } else {
  357.             /* Group still exists. Update the parentGroup backpointer. */
  358.             (**childInfo).parentGroup = index;
  359.             prevChildList = childList;
  360.             childList = (**childList).next;
  361.         }
  362.     }
  363.     return noErr;
  364. }
  365.  
  366.  
  367.  
  368. /*----------------------------------------------------------------------------
  369.     UpdateFullGroupWindow 
  370.     
  371.     Make sure that the Full Group List window corresponds to the changed 
  372.     full group list. It must be called whenever the full group list changes.
  373.     
  374.     Exit:    function result = error code.
  375. ----------------------------------------------------------------------------*/
  376.  
  377. static OSErr UpdateFullGroupWindow (void)
  378. {
  379.     TWindow **info;
  380.     Point thePt;
  381.     GrafPtr port;
  382.     OSErr err = noErr;
  383.  
  384.     GetPort(&port);
  385.     
  386.     err = AdjustFullGroupListChildWindows();
  387.     if (err != noErr) return err;
  388.     if (gFullGroupWindow != nil) {
  389.         info = (TWindow**)GetWRefCon(gFullGroupWindow);
  390.         (**info).groupArray = gFullGroupArray;
  391.         (**info).numGroups = gNumGroups;
  392.         err = MakeGroupList(gNumGroups, (**info).theList);
  393.         if (err != noErr) return err;
  394.         SetPt(&thePt, 0, 0);
  395.         MyLSetSelect(true, thePt, (**info).theList);
  396.         SetPort(gFullGroupWindow);
  397.         InvalRect(&gFullGroupWindow->portRect);
  398.     }
  399.     
  400.     SetPort(port);
  401.     return noErr;
  402. }
  403.  
  404.  
  405.  
  406. /*----------------------------------------------------------------------------
  407.     MergeNewGroupsIntoFullGroupList 
  408.     
  409.     Merge new groups into the full group list. Both lists must be sorted on 
  410.     entry. The full group list remains sorted on exit.
  411.     
  412.     Entry:    newGroupsArray = handle to new groups array.
  413.             numNew = number of new groups.
  414.     
  415.     Exit:    function result = error code.
  416. ----------------------------------------------------------------------------*/
  417.  
  418. static OSErr MergeNewGroupsIntoFullGroupList (TGroup **newGroupsArray, short numNew)
  419. {
  420.     short numLeftToInsert, numToMoveUp;
  421.     TGroup *fullListPtr, *newListPtr;
  422.     char *newName;
  423.     OSErr err = noErr;
  424.  
  425.     gNumGroups += numNew;
  426.     err = MySetHandleSize(gFullGroupArray, gNumGroups*sizeof(TGroup));
  427.     if (err != noErr) return err;
  428.     
  429.     numLeftToInsert = numNew;
  430.     fullListPtr = *gFullGroupArray + gNumGroups - numNew - 1;
  431.     newListPtr = *newGroupsArray + numNew - 1;
  432.     while (numLeftToInsert > 0) {
  433.         newName = *gGroupNames + newListPtr->nameOffset;
  434.         numToMoveUp = 0;
  435.         while (fullListPtr >= *gFullGroupArray &&
  436.             strcmp(newName, *gGroupNames + fullListPtr->nameOffset) <= 0) 
  437.         {
  438.             fullListPtr--;
  439.             numToMoveUp++;
  440.         }
  441.         if (numToMoveUp > 0)
  442.             BlockMoveData(fullListPtr + 1, fullListPtr + numLeftToInsert + 1, 
  443.                 numToMoveUp*sizeof(TGroup));
  444.         BlockMoveData(newListPtr, fullListPtr + numLeftToInsert, sizeof(TGroup));
  445.         newListPtr--;
  446.         numLeftToInsert--;
  447.     }
  448.     
  449.     return noErr;
  450. }
  451.  
  452.  
  453.  
  454. /*----------------------------------------------------------------------------
  455.     CheckForNewGroups 
  456.     
  457.     Check for any new groups created since last time we checked.
  458.  
  459.     Exit:    function result = error code.
  460.             *newGroupsArray = handle to group array, or nil if numNewGroups == 0.
  461.             *numNewGroups = number of groups in group array.
  462. ----------------------------------------------------------------------------*/
  463.  
  464. OSErr CheckForNewGroups (TGroup ***newGroupsArray, short *numNewGroups)
  465. {
  466.     OSErr err = noErr;
  467.     short len, nameWidth;
  468.     long numGroups, numNew, i;
  469.     Handle strings = nil;
  470.     char *p, *q;
  471.     long offset, savedGroupNamesSize;
  472.     TGroup **groupArray = nil;
  473.     TGroup *x;
  474.     CStr255 groupName;
  475.     GrafPtr port;
  476.     char state;
  477.     WindowPtr wind;
  478.     TWindow **info;
  479.     Handle unsubscribed;
  480.     
  481.     GetPort(&port);
  482.     savedGroupNamesSize = GetHandleSize(gGroupNames);
  483.     
  484.     err = DisplayStatusMessageNumber(kStrCheckingNewStatusMsg);
  485.     if (err != noErr) goto exit;
  486.         
  487.     /* Get the new group names from the server. */
  488.     
  489.     err = GetGroupNames(gPrefs.groupCheckTime, &strings, &numGroups);
  490.     if (err != noErr) goto exit;
  491.     
  492.     /* Check for duplicates. Filter out the groups which are already present in
  493.        the full group list. Check for too many new groups. */
  494.  
  495.     numNew = 0;
  496.     for (i = 0, p = *strings, q = *strings; i < numGroups; i++) {
  497.         len = strlen(q);
  498.         if (FindGroupIndex(q) == -1) {
  499.             if (gNumGroups + numNew >= 16000) break;
  500.             strcpy(p, q);
  501.             p += len+1;
  502.             numNew++;
  503.         }
  504.         q += len+1;
  505.     }
  506.     
  507.     MySetHandleSize(strings, p - *strings);
  508.     
  509.     if (i < numGroups) ErrorMessageNumber(kStrTooManyGroupsOnServer);
  510.     
  511.     if (numNew == 0) {
  512.         MyDisposeHandle(strings);
  513.         *numNewGroups = 0;
  514.         *newGroupsArray = nil;
  515.         GetDateTime(&gPrefs.groupCheckTime);
  516.         SetPort(port);
  517.         return noErr;
  518.     }
  519.     
  520.     /* Append the new group names to the end of gGroupNames. */
  521.     
  522.     offset = savedGroupNamesSize;
  523.     err = MyHandAndHand(strings, gGroupNames);
  524.     if (err != noErr) goto exit;
  525.     
  526.     /* Allocate and initialize the group array for the new groups. */
  527.     
  528.     err = MyNewHandle(numNew * sizeof(TGroup), &groupArray);
  529.     if (err != noErr) goto exit;
  530.     
  531.     for (i = 0, x = *groupArray, p = *gGroupNames + offset; i < numNew; i++, x++) {
  532.         InitializeGroupRecord(x, p - *gGroupNames);
  533.         p += strlen(p) + 1;
  534.     }
  535.     
  536.     /* Sort the new groups array. */
  537.     
  538.     gSortGroupNames = gGroupNames;
  539.     state = MyHGetState(groupArray);
  540.     MyHLock(groupArray);
  541.     err = FastQSort(*groupArray, numNew, sizeof(TGroup), 
  542.         (SortCmpFunction)GroupCompare);
  543.     MyHSetState(groupArray, state);
  544.     if (err != noErr) goto exit;
  545.     
  546.     /* Merge the new groups into the full group list. */
  547.  
  548.     err = MergeNewGroupsIntoFullGroupList(groupArray, numNew);
  549.     if (err != noErr) goto exit;
  550.     
  551.     err = UpdateFullGroupWindow();
  552.     if (err != noErr) goto exit;
  553.     
  554.     /* Update the last new groups check date and time. */
  555.     
  556.     GetDateTime(&gPrefs.groupCheckTime);
  557.     
  558.     /* Check to see if one of the new group names is now the widest group 
  559.        name in the full group list window. */
  560.     
  561.     SetPort(gFullGroupWindow);
  562.     if (gPrefs.maxGroupNameWidth > 0) {
  563.         for (i = 0; i < numNew; i++) {
  564.             x = &(*groupArray)[i];
  565.             strcpy(groupName, *gGroupNames + x->nameOffset); 
  566.             nameWidth = TextWidth(groupName, 0, strlen(groupName));
  567.             if (nameWidth > gPrefs.maxGroupNameWidth) gPrefs.maxGroupNameWidth = nameWidth;
  568.         }
  569.     }
  570.     
  571.     /* Add each new group name to the unsubscribed list of each open user group
  572.        list window. */
  573.        
  574.     wind = FrontWindow();
  575.     while (wind != nil) {
  576.         if (GetMyWindowKind(wind) == kGroup) {
  577.             info = (TWindow**)GetWRefCon(wind);
  578.             unsubscribed = (**info).unsubscribed;
  579.             if (unsubscribed != nil) {
  580.                 for (i = 0; i < numNew; i++) {
  581.                     x = &(*groupArray)[i];
  582.                     strcpy(groupName, *gGroupNames + x->nameOffset);
  583.                     err = AddGroupToUnsubscribedList(groupName, unsubscribed);
  584.                     if (err != noErr) goto exit;
  585.                 }
  586.             }
  587.         }
  588.         wind = (WindowPtr)((WindowPeek)wind)->nextWindow;
  589.     }
  590.     
  591.     /* Return. */
  592.     
  593.     *newGroupsArray = groupArray;
  594.     *numNewGroups = numNew;
  595.     SetPort(port);
  596.     return noErr;
  597.     
  598. exit:
  599.  
  600.     MyDisposeHandle(strings);
  601.     MyDisposeHandle(groupArray);
  602.     MySetHandleSize(gGroupNames, savedGroupNamesSize);
  603.     SetPort(port);
  604.     return err;
  605. }
  606.  
  607.  
  608.  
  609. /*----------------------------------------------------------------------------
  610.     DoCheckForNewGroups
  611.  
  612.     Handle the "Check for New Groups" command.
  613.     
  614.     Exit:    function result = error code.
  615. ----------------------------------------------------------------------------*/
  616.  
  617. OSErr DoCheckForNewGroups (void)
  618. {
  619.     TGroup **groupArray;
  620.     short numGroups;
  621.     WindowPtr wind;
  622.     OSErr err = noErr;
  623.  
  624.     err = CheckForNewGroups(&groupArray, &numGroups);
  625.     if (err != noErr) return err;
  626.     if (numGroups > 0) {
  627.         return MakeNewGroupsWindow(groupArray, numGroups, &wind);
  628.     } else {
  629.         return noErr;
  630.     }
  631. }
  632.  
  633.  
  634.  
  635. /*----------------------------------------------------------------------------
  636.     DoCheckForDeletedGroups 
  637.     
  638.     Handle the "Check for Deleted Groups" command.
  639.     
  640.     Exit:    function result = error code.
  641. ----------------------------------------------------------------------------*/
  642.  
  643. OSErr DoCheckForDeletedGroups (void)
  644. {
  645.     Handle strings = nil;
  646.     Handle deleted = nil;
  647.     long deletedNext, deletedAllocated, offset;
  648.     short index, len, nameWidth, numDel, numToMoveDown;
  649.     long numGroups, i;
  650.     TGroup *x, *prev, *cur, *curEnd;
  651.     char *p;
  652.     OSErr err = noErr;
  653.     CStr255 groupName;
  654.     GrafPtr port;
  655.     Str255 title;
  656.     WindowPtr wind;
  657.     
  658.     GetPort(&port);
  659.  
  660.     err = DisplayStatusMessageNumber(kStrCheckingDelStatusMsg);
  661.     if (err != noErr) goto exit;
  662.     
  663.     /* Get a list of all group names from the server. */
  664.     
  665.     err = GetGroupNames(0, &strings, &numGroups);
  666.     if (err != noErr) goto exit;
  667.     
  668.     /* Mark all the groups in the full group list with status = 'd'. Then walk
  669.        the fresh full group list we just got from the server and mark all
  670.        the groups which still exist with status = ' '. This leaves just the
  671.        deleted groups marked with status = 'd'. */
  672.     
  673.     for (i = 0, x = *gFullGroupArray; i < gNumGroups; i++, x++) x->status = 'd';
  674.     
  675.     for (i = 0, offset = 0; i < numGroups; i++) {
  676.         err = GiveTime(false);
  677.         if (err != noErr) goto exit;
  678.         p = *strings + offset;
  679.         index = FindGroupIndex(p);
  680.         if (index != -1) (*gFullGroupArray)[index].status = ' ';
  681.         offset += strlen(p) + 1;
  682.     }
  683.     MyDisposeHandle(strings);
  684.     strings = nil;
  685.     
  686.     /* Allocate a buffer to hold the names of the deleted groups for display
  687.        to the user. */
  688.     
  689.     err = MyNewHandle(0, &deleted);
  690.     if (err != noErr) goto exit;
  691.     deletedNext = deletedAllocated = 0;
  692.     
  693.     /* Walk the full group list. Copy the names of the deleted groups to the 
  694.        buffer. Also check to see if the group with the widest name in the full
  695.        group list window has been deleted. */
  696.     
  697.     SetPort(gFullGroupWindow);
  698.     numDel = 0;
  699.     for (i = 0; i < gNumGroups; i++) {
  700.         x = &(*gFullGroupArray)[i];
  701.         if (x->status == 'd') {
  702.             err = GiveTime(false);
  703.             if (err != noErr) goto exit;
  704.             numDel++;
  705.             strcpy(groupName, *gGroupNames + x->nameOffset);
  706.             len = strlen(groupName);
  707.             if (deletedNext + len + 1 > deletedAllocated) {
  708.                 deletedAllocated += 1000;
  709.                 err = MySetHandleSize(deleted, deletedAllocated);
  710.                 if (err != noErr) goto exit;
  711.             }
  712.             strcpy(*deleted + deletedNext, groupName);
  713.             deletedNext += len+1;
  714.             *(*deleted + deletedNext - 1) = CR;
  715.             if (gPrefs.maxGroupNameWidth > 0) {
  716.                 nameWidth = TextWidth(groupName, 0, len);
  717.                 if (nameWidth >= gPrefs.maxGroupNameWidth) gPrefs.maxGroupNameWidth = 0;
  718.             }
  719.         }
  720.     }
  721.     
  722.     /* If there aren't any deleted groups, issue a note message and return. */
  723.     
  724.     if (numDel == 0) {
  725.         MyDisposeHandle(deleted);
  726.         NoteMessageNumber(kStrNoDelGroups);
  727.         return noErr;
  728.     }
  729.     
  730.     MySetHandleSize(deleted, deletedNext);
  731.     
  732.     /* Remove the deleted groups from the full group list. */
  733.     
  734.     prev = *gFullGroupArray;
  735.     cur = *gFullGroupArray;
  736.     curEnd = *gFullGroupArray + gNumGroups;
  737.     numDel = 0;
  738.     while (cur < curEnd) {
  739.         numToMoveDown = 0;
  740.         while (cur < curEnd && cur->status != 'd') {
  741.             cur++;
  742.             numToMoveDown++;
  743.         }
  744.         if (numDel > 0 && numToMoveDown > 0)
  745.             BlockMoveData(prev + numDel, prev, numToMoveDown*sizeof(TGroup));
  746.         prev += numToMoveDown;
  747.         numDel++;
  748.         cur++;
  749.     }
  750.     
  751.     /* Update the full group list window, display the deleted group names to the
  752.        user in a text window, and return. */
  753.     
  754.     gNumGroups -= numDel;
  755.     MySetHandleSize(gFullGroupArray, gNumGroups*sizeof(TGroup));
  756.     err = UpdateFullGroupWindow();
  757.     if (err != noErr) goto exit;
  758.     GetPString(kStrDelGroupsWindTitle, title);
  759.     err = MakeNewTextWindow(title, 0, nil, deleted, &wind);
  760.     if (err != noErr) goto exit;
  761.     
  762.     MyDisposeHandle(deleted);
  763.     SetPort(port);
  764.     return noErr;
  765.     
  766. exit:
  767.  
  768.     MyDisposeHandle(strings);
  769.     MyDisposeHandle(deleted);
  770.     SetPort(port);
  771.     return err;
  772. }
  773.  
  774.  
  775.  
  776. /*----------------------------------------------------------------------------
  777.     MustCloseBeforeRebuildDialog 
  778.     
  779.     Present the "must close windows before rebuilding full group list" dialog.
  780.             
  781.     Exit:    function result = error code.
  782. ----------------------------------------------------------------------------*/
  783.  
  784. static OSErr MustCloseBeforeRebuildDialog (void)
  785. {
  786.     OSErr err = noErr;
  787.     DialogPtr dlg = nil;
  788.     short item;
  789.     
  790.     err = MyGetNewDialog(kMustCloseBeforeRebuildDlg, ok, cancel, &dlg);
  791.     if (err != noErr) return err;
  792.     SysBeep(0);
  793.     MyModalDialog(dlg, gDialogFilterUPP, &item);
  794.     err = DoClose(dlg);
  795.     if (err != noErr) return err;
  796.     if (item == cancel) return userCanceledErr;
  797.     return noErr;
  798. }
  799.  
  800.  
  801.  
  802. /*----------------------------------------------------------------------------
  803.     DoRebuildFullGroupList 
  804.     
  805.     Handle the "Rebuild Full Group List" command.
  806.  
  807.     Exit:    function result = error code.
  808. ----------------------------------------------------------------------------*/
  809.  
  810. OSErr DoRebuildFullGroupList (void)
  811. {
  812.     TGroup **newFullGroupArray = nil;
  813.     long newNumGroups;
  814.     Handle newGroupNames = nil;
  815.     TGroup *x;
  816.     long i;
  817.     char *p;
  818.     OSErr err = noErr;
  819.     WindowPtr wind;
  820.     TWindowKind kind;
  821.     Boolean promptBeforeClose = true;
  822.     
  823.     /* Close all group windows except for the full group list window, and close
  824.        all subject windows. We must do this because group and subject window data 
  825.        structures contain offsets into the old group names gGroupNames, which is 
  826.        about to be blown away and replaced. */
  827.     
  828.     while (true) {
  829.         wind = FrontWindow();
  830.         while (wind != nil) {
  831.             kind = GetMyWindowKind(wind);
  832.             if ((kind == kGroup && wind != gFullGroupWindow) ||
  833.                 kind == kSubject) break;
  834.             wind = (WindowPtr)((WindowPeek)wind)->nextWindow;
  835.         }
  836.         if (wind == nil) break;
  837.         if (promptBeforeClose) {
  838.             err = MustCloseBeforeRebuildDialog();
  839.             if (err != noErr) goto exit;
  840.             promptBeforeClose = false;
  841.         }
  842.         err = DoClose(wind);
  843.         if (err != noErr) goto exit;
  844.     }
  845.     
  846.     /* Display the status message. */
  847.     
  848.     err = DisplayStatusMessageNumber(kStrGetFullStatusMsg);
  849.     if (err != noErr) goto exit;
  850.     
  851.     /* Get a list of all group names from the server. */
  852.     
  853.     err = GetGroupNames(0, &newGroupNames, &newNumGroups);
  854.     if (err != noErr) goto exit;
  855.     
  856.     /* Check for too many groups. */
  857.     
  858.     if (newNumGroups > 16000) {
  859.         newNumGroups = 16000;
  860.         ErrorMessageNumber(kStrTooManyGroupsOnServer);
  861.     }
  862.  
  863.     /* Allocate the new full group array. */
  864.  
  865.     err = MyNewHandle(newNumGroups * sizeof(TGroup), &newFullGroupArray);
  866.     if (err != noErr) goto exit;
  867.     
  868.     /* Initialize the new full group array. */
  869.     
  870.     for (i = 0, x = *newFullGroupArray, p = *newGroupNames; i < newNumGroups; i++, x++) {
  871.         InitializeGroupRecord(x, p - *newGroupNames);
  872.         p += strlen(p) + 1;
  873.     }
  874.     MySetHandleSize(newGroupNames, p - *newGroupNames);
  875.     
  876.     /* Sort the new full group array. */
  877.     
  878.     err = SortFullGroupArray(newFullGroupArray, newNumGroups, newGroupNames);
  879.     if (err != noErr) goto exit;
  880.     
  881.     /* Make the new full group array and group names the real ones. Dispose the old
  882.        ones. */
  883.        
  884.     MyDisposeHandle(gFullGroupArray);
  885.     gFullGroupArray = newFullGroupArray;
  886.     newFullGroupArray = nil;
  887.     gNumGroups = newNumGroups;
  888.     MyDisposeHandle(gGroupNames);
  889.     gGroupNames = newGroupNames;
  890.     newGroupNames = nil;
  891.     
  892.     /* Update the full group list window and return. */
  893.     
  894.     gPrefs.maxGroupNameWidth = 0;
  895.     err = UpdateFullGroupWindow();
  896.     if (err != noErr) goto exit;
  897.     GetDateTime(&gPrefs.groupCheckTime);
  898.     return noErr;
  899.     
  900. exit:
  901.  
  902.     MyDisposeHandle(newFullGroupArray);
  903.     MyDisposeHandle(newGroupNames);
  904.     return err;
  905. }
  906.